#include "Image.h"
#include "libpng/png.h"
#include <iostream>
#include <vector>
#include <fstream>

Image::Image(){
    m_Data = new Pixelf[32*32];
    m_SizeX = 32;
    m_SizeY = 32;
}

Image::Image(int sizeX, int sizeY){
    m_Data = new Pixelf[sizeX*sizeY];
    m_SizeX = sizeX;
    m_SizeY = sizeY;
}

Image::~Image(){
    delete m_Data;
}

Pixelf* Image::getData(){
    return m_Data;
}

Pixelf* Image::getAt(int x,int y){
    return &m_Data[y*m_SizeX+x];
}

void PngErrorHandler(png_structp Png, png_const_charp Message)
{
    std::cerr << "Erreur dans l'écriture de l'image: " << 
        Message << std::endl;
    longjmp(Png->jmpbuf, 1);
}

Image Image::resampleByFactor(int k){
    Image newImg(m_SizeX/k,m_SizeY/k);
    float k2 = (float)(k*k);
    for (int x=0;x<m_SizeX;x++){
    for (int y=0;y<m_SizeY;y++){
        *(newImg.getAt(x/k,y/k))+= *(this->getAt(x,y))/k2;
    }}
    return newImg;
}


bool Image::save(const char* path){
    FILE* File = fopen(path, "wb");
    if (!File)
    {
        std::cerr << 
            "Erreur dans la sauvegarde: le fichier ne peut être ouvert" <<
            std::endl;
        return false;
    }

    png_structp Png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
                                              NULL,
                                              &PngErrorHandler,
                                              NULL);
    if (!Png)
    {
        fclose(File);
        std::cerr << "Erreur dans la sauvegarde: \"cannot allocate PNG write structure\"" << std::endl;
        return false;
    }

    // On crée la structure d'info
    png_infop PngInfo = png_create_info_struct(Png);
    if (!PngInfo)
    {
        fclose(File);
        png_destroy_write_struct(&Png, NULL);
        std::cerr << "Erreur dans la sauvegarde: \"cannot allocate PNG info structure\"" << std::endl;
        return false;
    }

    // On vérifie si tout est encore bon.
    if (setjmp(Png->jmpbuf))
    {
        png_destroy_write_struct(&Png, &PngInfo);
        return false;
    }

    // On passe le FILE* à png
    png_init_io(Png, File);

    // on creele IHDR, RGB
    png_set_IHDR(Png, PngInfo, m_SizeX, m_SizeY, 8,
                 PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
                 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

    // on écrit le header
    png_write_info(Png, PngInfo);

    // on cree un copie en byte de note image
    png_byte* PixelsPtr = new png_byte[m_SizeX*m_SizeY*3];
    png_byte* BasePtr = PixelsPtr;
    float* dataPtr = (float*)m_Data;
    for (int i=0;i<m_SizeX*m_SizeY*3;++i){
        PixelsPtr[i] = dataPtr[i]*255;
    }
    // On rentre chaque ligne de donnée
    std::vector<png_byte*> RowPointers(m_SizeY);
    for (int i = 0; i < m_SizeY; ++i)
    {
        RowPointers[i] = PixelsPtr;
        PixelsPtr += m_SizeX * 3;
    }

    png_set_rows(Png, PngInfo, &RowPointers[0]);
    png_write_png(Png, PngInfo, PNG_TRANSFORM_IDENTITY, NULL);

    // termine l'écriture du fichier
    png_write_end(Png, PngInfo);

    // cleanup
    png_destroy_write_struct(&Png, &PngInfo);
    fclose(File);
    delete BasePtr;

    return true; // c'est réussi
}
#define PNGSIGSIZE 8

bool validatePNG(std::istream& source) {

    //La signature d'un PNG est de 8 bytes
    png_byte pngsig[PNGSIGSIZE];
    int is_png = 0;

    //On lit 
    source.read((char*)pngsig, PNGSIGSIZE);
    if (!source.good()) return false;

    //On laisse LibPNG vérifier la signature
    is_png = png_sig_cmp(pngsig, 0, PNGSIGSIZE);
    return (is_png == 0);
}
void pngReadData(png_structp pngPtr, png_bytep data, png_size_t length) {
    //Ici on retrouve les pointeurs vers les données, qui est en fait le pointeur
    // std::istream qu'on a passé à libPNG
    png_voidp a = png_get_io_ptr(pngPtr);
    // on cast a et on remplit data de length bytes
    ((std::istream*)a)->read((char*)data, length);
}

bool Image::loadPNG(const char* path){
    std::ifstream source(path,std::ifstream::in);

    if (!validatePNG(source)) {
        std::cerr << "Erreur: Cette image n'est pas un PNG:"<< path << std::endl;
        return false;
    }

    // on cree une structure de lecture
    png_structp pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!pngPtr) {
        std::cerr << "Erreur interne de libPNG lors de "<< path << std::endl;
        return false;
    }
    
    // on crée la structure qui contient les infos sur le PNG
    png_infop infoPtr = png_create_info_struct(pngPtr);
    if (!infoPtr) {
        std::cerr << "Erreur: Impossible de créer une structure d'infos pour"<< path << std::endl;
        png_destroy_read_struct(&pngPtr, (png_infopp)0, (png_infopp)0);
        return false;
    }

    // ces pointeurs vont contenir nos données
    png_bytep* rowPtrs = NULL;
    unsigned char* data = NULL;

    if (setjmp(png_jmpbuf(pngPtr))) {
        // il y a eu une erreur, libPNG jump ici, et on nettoie tout
        png_destroy_read_struct(&pngPtr, &infoPtr,(png_infopp)0);
        if (rowPtrs != NULL) delete [] rowPtrs;
        if (data != NULL) delete [] data;
        
        std::cout << "Erreur: Problème lors de la lecture de l'image PNG";
        
        return false;
    }

    // On donne a libpng la bonne fonction pour lire des données depuis le disque
    png_set_read_fn(pngPtr,(void*)&source, pngReadData);

    // on avance le pointeurs de 8 bytes
    png_set_sig_bytes(pngPtr, PNGSIGSIZE);
    // on lit les informations du fichier
    png_read_info(pngPtr, infoPtr);
    png_uint_32 imgWidth =  png_get_image_width(pngPtr, infoPtr);
    png_uint_32 imgHeight = png_get_image_height(pngPtr, infoPtr);

    //bits par channel
    png_uint_32 bitdepth   = png_get_bit_depth(pngPtr, infoPtr);
    //nombre de channels
    png_uint_32 channels   = png_get_channels(pngPtr, infoPtr);
    // type de palette (RBG, RGBA)
    png_uint_32 color_type = png_get_color_type(pngPtr, infoPtr);
    switch (color_type) {
        case PNG_COLOR_TYPE_PALETTE:
            png_set_palette_to_rgb(pngPtr);
            channels = 3;           
            break;

        case PNG_COLOR_TYPE_GRAY:
            if (bitdepth < 8)
                png_set_gray_1_2_4_to_8(pngPtr);
            bitdepth = 8;
            break;
    }


    if (png_get_valid(pngPtr, infoPtr, PNG_INFO_tRNS)) {
        png_set_tRNS_to_alpha(pngPtr);
        channels+=1;
    }

    rowPtrs = new png_bytep[imgHeight];
    // on crée le buffer de l'image
    data = new unsigned char[imgWidth * imgHeight * bitdepth * channels / 8];
    // Taille en bytes d'une rangée
    const unsigned int stride = imgWidth * bitdepth * channels / 8;
    
    // on remplit les pointeurs de chaque rangée
    for (size_t i = 0; i < imgHeight; i++) {
        png_uint_32 q = (i) * stride;
        rowPtrs[i] = (png_bytep)data + q;
    }
    
    // on passe les pointeurs vers les rangées, et on lit finalement l'image
    png_read_image(pngPtr, rowPtrs);

    if (m_Data!=NULL){
        delete[] m_Data;
    }
    
    //maintenant on copie les donnes 24bit RGB en float
    m_SizeX = imgWidth;
    m_SizeY = imgHeight;
    m_Data = new Pixelf[m_SizeX*m_SizeY];
    float* dataPtr = (float*)m_Data;
    for (int i=0;i<m_SizeX*m_SizeY*3;++i){
        dataPtr[i] = ((float)data[i])/255.0;
    }
    //On supprime les donnees dont on a plus besoin
    delete[] (png_bytep)rowPtrs;
    delete[] data;
    //petit clean up
    png_destroy_read_struct(&pngPtr, &infoPtr,(png_infopp)0);
    return true;
}
